#!/usr/bin/python
# -*- coding: utf-8 -*-

u"""
"""

__version__ = '$Id: test_template.py 142 2008-07-27 15:28:55Z DR0ID $'


import unittest

import sys
import os
# find the local copy
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))


from bipy.gamebase import GameState as OrigGameState
from bipy.gamebase import GameBase

#-------------------------------------------------------------------------------
class TestState(OrigGameState):

    def __init__(self):
        super(TestState, self).__init__()
        self.call_sequence = []
        self.args = None
        self.init_args = None
        self._game = 6666666666666666
        self.init_event = False
        self.exit_event = False
        self.event_init += self.listen_to_init
        self.event_exit += self.listen_to_exit
        self.event_update += self.update

    def init(self, a_game, *args, **kwargs):
        """init the state and should call load"""
        #print '?'*30, a_game, args, kwargs
        self.call_sequence.append('init')
        self.init_args = (a_game, args, kwargs)
        self._game = a_game

    def load(self):
        """all needed resources should be loaded here"""
        self.call_sequence.append('load')
        self.args = None

    def unload(self):
        """all resources should be freed here"""
        self.call_sequence.append('unload')
        self.args = None

    def exit(self):
        """is called before the state is exited, should call unload"""
        self.call_sequence.append('exit')
        self.args = None

    def render(self, aTimeDelta):
        """renders the state"""
        self.call_sequence.append('render')
        self.args = aTimeDelta

    def handle_events(self, *args, **kwargs):
        self.call_sequence.append('handle_events')
        self.args = (args, kwargs)

    def update(self, aTimeDelta, aTime):
        """updates the state"""
        self.call_sequence.append('update')
        self.args = aTimeDelta

    def listen_to_init(self, *args, **kwargs):
        self.init_event = True

    def listen_to_exit(self, *args, **kwargs):
        self.exit_event = True

#-------------------------------------------------------------------------------
class TestState2(TestState):

    def __init__(self):
        super(TestState2, self).__init__()

    def update(self, aTimeDelta, aTime):
        super(TestState2, self).update(aTimeDelta, aTime)

#-------------------------------------------------------------------------------
class TestState3(TestState2):

    def __init__(self):
        super(TestState3, self).__init__()

    def update(self, aTimeDelta, aTime):
        super(TestState3, self).update(aTimeDelta, aTime)
        self._game.quit()

#-------------------------------------------------------------------------------
class EventTestState(TestState):

    class EventLogger(object):
        
        order = []
        log = {} # {event_*:[(args, kwargs), (...)]}
        
        def __init__(self, name):
            self.name = name

        def __call__(self, *args, **kwargs):
            log = self.log.get(self.name, [])
            log.append((args, kwargs))
            self.log[self.name] = log
            self.order.append((self.name, args, kwargs))

        @classmethod
        def clear(cls):
            cls.log = {}
            cls.order = []

        @classmethod
        def print_data(cls):
            print u'\n'
            print '-'*10, u'num calls per type', '-'*10
            for e in cls.log:
                print e, u':\t', cls.log[e]
            print '-'*10, u'call order ', '-'*10
            for e in cls.order:
                print e
            print u'='*10

        @classmethod
        def print_time_statistics(cls):
            values = sorted(cls.log.get('event_update', []))
            stat = {}
            for args, kwargs in values:
                dt, t = args
                stat[dt] = stat.get(dt, 0) + 1
            print u'-'*10, u'time statistics:', u'-'*10, u'\n'
            for k in stat:
                sys.stdout.write(u"%s ms: % i \t\t %f %% %s" % (k, stat[k], 100./len(values)*stat[k], os.linesep))
            print u'-'*20

    def __init__(self, *args, **kwargs):
        super(EventTestState, self).__init__(*args, **kwargs)
        # set an on_* callable for each event_* and log the args to
        # self.log[event_*] = [args, kwargs]
        handler_name = 'on_'
        for s in dir(self):
            if s.startswith('event_'):
                name = '_'.join(s.split('_')[1:])
                name = handler_name + name
                # create listener and set it 
                evtlogger = self.EventLogger(s)
                setattr(self, name, evtlogger)
                # register logger at event
                subj = getattr(self, s)
                subj += getattr(self, name)
        self.count = 0
        self.max_count = 10
        self.game_base = None
        self.event_update += self.on_update2
        # remove scheduler
        self.event_activate -= self.scheduler.on_activate
        self.event_deactivate -= self.scheduler.on_deactivate
        self.scheduler = None
        

    # overwrite default on_close handler
    def on_close(self, game_base):
        pass

    def on_update2(self, dt, t):
        self.count += 1
        #print self.count
        if self.count >= self.max_count:
            self.game_base.quit()
        if 3 == self.count:
            # post all test events
            self.post_events()

    def post_events(self):
        pass

                    
    def init(self, game_base):
        self.game_base = game_base
#    Default handlers:
#        self.on_close listens to the event_quit event
#        self.init listens to event_init, init state specific things
#        self.exit listens to event_exit, clean up state specific things
#    Internal use for the state scheduler:
#        self._on_activate_scheduler listens to event_activate_state
#        self._on_deactivate_scheduler listens to event_deactivate_state

    def len_test(self, test_case):
        for event_type in self.EventLogger.log:
            if not len(self.EventLogger.log[event_type]):
                msg = u"event %s has not been sent event once!!" %(event_type)
                test_case.fail(msg)

    def test_shared_events(self, test_case):
        #-- state events --#
        #test_case.failUnlessEqual(self.EventLogger.log['event_update'], [])
        test_case.failUnlessEqual(self.EventLogger.log['event_init'], [((self.game_base,), {})])# on_init(game_base, *args, **kwargs)
        test_case.failUnlessEqual(self.EventLogger.log['event_exit'], [(tuple(), {})])# on_exit()
        test_case.failUnlessEqual(self.EventLogger.log['event_activate_state'], [((self.game_base,), {})])# on_activate(game_base)
        test_case.failUnlessEqual(self.EventLogger.log['event_deactivate_state'], [(tuple([self.game_base]), {})])# on_deactivate(game_base)
        #test_case.failUnlessEqual(self.EventLogger.log['event_idle'], [])# on_idle()
        
        #-- input events --#

        #-- shared events --#
        test_case.failUnlessEqual(self.EventLogger.log['event_quit'], [((self.game_base, ), {})]) #               pygame.QUIT,           #  none
        test_case.failUnlessEqual(self.EventLogger.log['event_video_expose'], [(tuple(), {})]) #      pygame.VIDEOEXPOSE,    #  none
        test_case.failUnlessEqual(self.EventLogger.log['event_expose'], [(tuple(), {})])  # = self.event_video_expose
        test_case.failUnlessEqual(self.EventLogger.log['event_activate'], [(tuple(), {}), (tuple(), {})])   # The window was activated (got keyboard focus)
        test_case.failUnlessEqual(self.EventLogger.log['event_deactivate'], [(tuple(), {}), (tuple(), {})]) # The window was deactivated (lost keyboard focus)
        test_case.failUnlessEqual(self.EventLogger.log['event_key_down'], [((97, 768, 'a'), {})]) #          pygame.KEYDOWN,        #  unicode, key, mod
        test_case.failUnlessEqual(self.EventLogger.log['event_key_up'], [((98, 768), {})]) #            pygame.KEYUP,          #  key, mod
        test_case.failUnlessEqual(self.EventLogger.log['event_key_press'], [((97, 768, 'a'), {})]) # = self.event_key_down
        test_case.failUnlessEqual(self.EventLogger.log['event_key_release'], [((98, 768), {})]) # = self.event_key_up
        # FIXME: buttons as bitwise field
        test_case.failUnlessEqual(self.EventLogger.log['event_mouse_drag'], [(((33,33,), (3,3,), (1, 0, 0,), 4096), {}),
                                                                             (((33,33,), (3,3,), (0, 1, 0,), 4096), {}),
                                                                             (((33,33,), (3,3,), (0, 0, 1,), 4096), {})]) #            x, y, dx, dy, buttons
        #test_case.failUnlessEqual(self.EventLogger.log['event_mouse_enter'], [(tuple(), {})]) # x, y
        #test_case.failUnlessEqual(self.EventLogger.log['event_mouse_leave'], [(tuple(), {})]) # x, y
        test_case.failUnlessEqual(self.EventLogger.log['event_mouse_motion'], [(((22,22), (2,2)), {})]) #      pygame.MOUSEMOTION,    #  pos, rel
        test_case.failUnlessEqual(self.EventLogger.log['event_mouse_button_up'], [(((44, 44), 2, 4096), {})]) #    pygame.MOUSEBUTTONUP,  #  pos, button, mod
        test_case.failUnlessEqual(self.EventLogger.log['event_mouse_button_down'], [(((55, 55), 3, 4096), {})]) #  pygame.MOUSEBUTTONDOWN,#  pos, button, mod
        test_case.failUnlessEqual(self.EventLogger.log['event_video_resize'], [((800, 600), {})]) #      pygame.VIDEORESIZE,    #  size, w, h
        test_case.failUnlessEqual(self.EventLogger.log['event_resize'], [((800, 600), {})])  # = self.event_video_resize

#    def test_pyglet_events(self, test_case):
#        #-- pyglet events --#
#        test_case.failUnlessEqual(self.EventLogger.log['event_close'], [(tuple(), {})])  # = self.event_quit
#        test_case.failUnlessEqual(self.EventLogger.log['event_context_lost'], [(tuple(), {})])       # the window's GL context was lost
#        test_case.failUnlessEqual(self.EventLogger.log['event_context_state_lost'], [(tuple(), {})]) # the state of the window's GL context was lost
#        test_case.failUnlessEqual(self.EventLogger.log['event_draw'], [(tuple(), {})])               
#        test_case.failUnlessEqual(self.EventLogger.log['event_hide'], [(tuple(), {})])               # window was hidden
#        test_case.failUnlessEqual(self.EventLogger.log['event_mouse_scroll'], [(tuple(), {})])  # x, y, scroll_x, scroll_y
#        test_case.failUnlessEqual(self.EventLogger.log['event_move'], [(tuple(), {})]) # x, y The window was moved
#        test_case.failUnlessEqual(self.EventLogger.log['event_show'], [(tuple(), {})]) # The window was shown
#        test_case.failUnlessEqual(self.EventLogger.log['event_text'], [(tuple(), {})]) # The user input some text
#        test_case.failUnlessEqual(self.EventLogger.log['event_text_motion'], [(tuple(), {})]) # The user moved the text input cursor
#        test_case.failUnlessEqual(self.EventLogger.log['event_text_motion_select'], [(tuple(), {})]) # The user moved the text input cursor while extending the selection
        
#-------------------------------------------------------------------------------
class TestGameBase(unittest.TestCase):

    def setUp(self):
        self.game = GameBase(TestState())
        self.state = self.game.get_game_state()

#    def tearDown(self):
#        print 'call_seq', self.state.call_sequence

    def test_00_get_state(self):
        state = self.game.get_game_state()
        self.failUnlessEqual(state, self.state)


    def test_01_state_init_called(self):
        self.game._update(0,0)
        self.failUnless(self.state.init_event)
        self.game.set_game_state(TestState2())
        self.game._update(0.0, 0.0) # needed because of deferred state change
        self.failUnless(self.state.exit_event)

    def test_02_update(self):
        v_time = 33.3
        self.game._update(v_time, 0)
        self.failUnlessEqual(self.state.call_sequence[-1], 'update')
        self.failUnlessEqual(self.state.args, v_time)

    def test_03_run(self):
        game = GameBase(TestState3())
        game.set_game_state(TestState3())
        game.run()
        state = game.get_game_state()
        #print state.call_sequence
        seq = ['init', 'render', 'update', 'exit']
        self.failUnlessEqual(state.call_sequence, seq)

    def test_04_quit_sets_flag(self):
        self.failIfEqual(self.game._running, True)
        self.failUnlessEqual(self.game._running, False)
        self.game.quit()
        self.failIfEqual(self.game._running, True)
        self.failUnlessEqual(self.game._running, False)

    def test_05_set_state(self):
        old_state = self.game.get_game_state()
        new_state = TestState()
        args = 999
        kwargs = {'name': 666}
        self.game.set_game_state(new_state, args, **kwargs)
        self.game._update(1, 0)
        # test if exit has been called on old state
        #print 'old', old_state.call_sequence
        #print 'new', new_state.call_sequence
        self.failUnlessEqual(old_state.call_sequence[-1], 'exit')
        # test if init of new_state has been callded
        self.failUnlessEqual(new_state.call_sequence[-3], 'init')
        self.failUnlessEqual(new_state.call_sequence[-1], 'update')
        # test if init has been called with the passed arguments
        #print 'args', new_state.args
        self.failUnlessEqual(new_state.init_args, (self.game, (args,), kwargs))

    def on_framerate_changed(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

    def test_06_property_fps(self):
        self.game.event_framerate_changed += self.on_framerate_changed
        self.game.framerate = 10
        self.failUnlessEqual(10, self.game.framerate)
        self.failUnlessEqual(self.args, (100,)) # tpf = 1000.0/fps
        self.game.framerate = 0
        self.failUnlessEqual(0, self.game.framerate)
        self.failUnlessEqual(self.args, (-1.0,))

    def test_07_push_pop(self):
        vstate = TestState()
        self.game.push(vstate)
        self.failUnless(self.state == self.game.get_game_state())
        self.game._update(1, 0)
        # state is updated after one call to udpate(..)
        self.failUnless(vstate == self.game.get_game_state())
        self.game.pop()
        self.failUnless(vstate == self.game.get_game_state())
        self.game._update(1, 0)
        self.failUnless(self.state == self.game.get_game_state())

    def test_08_push_pop2(self):
        vstate = TestState2()
        self.game.push(vstate)
        self.game.pop()
        self.failUnless(self.state == self.game.get_game_state())
        self.failIf(self.game._running)
        self.game.pop()
        self.game._update(1, 0)
        self.failUnless(self.game._running)

    def test_09_push_push_pop_pop(self):
        vstate1 = TestState2()
        vstate2 = TestState2()
        self.game.push(vstate1)
        self.game.push(vstate2)
        self.game._update(1, 0)
        self.failUnless(vstate2 == self.game.get_game_state()) # deferred
        self.game.pop()
        self.game.pop()
        self.game._update(1, 0)
        self.failUnless(self.state == self.game.get_game_state())

    def test_10_push_exchange(self):
        vstate1 = TestState2()
        vstate2 = TestState2()
        self.game.push(vstate1)
        self.game.set_game_state(vstate2)
        self.game._update(1, 0)
        self.failUnless(vstate2 == self.game.get_game_state()) # deferred
        self.game.pop()
        self.game._update(1,0)
        self.failIf(vstate2 == self.game.get_game_state()) # deferred
        self.failIf(vstate1 == self.game.get_game_state()) # deferred
        


#-------------------------------------------------------------------------------
## pyglet ##
#try:
##    import pyglet
##    from bipy.pygletutils import pygletbase
    
##    class TestPygletGambeBase(TestGameBase):

##        def setUp(self):
##            self.game = pygletbase.PygletGameBase(TestState())
##            self.state = self.game.get_game_state()
#    pass
#except ImportError:
#    import warnings
#    warnings.warn(UserWarning(u"Pyglet not present, PygletGameBase not tested!"))

#-------------------------------------------------------------------------------

if __name__ == '__main__':
    unittest.main()
